/*
	NounFighter.cpp
	(c)2000 Palestar Inc, Richard Lyle
*/


#include "Debug/Assert.h"
#include "Math/Helpers.h"
#include "NounFighter.h"
#include "NounShip.h"
#include "NounNebula.h"
#include "GadgetBeamWeapon.h"
#include "GameContext.h"

//---------------------------------------------------------------------------------------------------

const int		FIGHTER_UPDATE_RATE = TICKS_PER_SECOND * 1.5f;
const int		FIGHTER_ENERGY = 100;
const float		FIGHTER_EVADE_DISTANCE = 100.0f;			// distance when to begin evading target
const float		FIGHTER_ATTACK_DISTANCE = 450.0f;			// distance when evade is switched back to attack mode
const float		FIGHTER_GROUPING_DISTANCE = 170.0f;			// maximum distance between two figthers to enter formation
const float		FIGHTER_LEADER_DISTANCE = 70.0f;			// distance fighter maintains to it's group leader
const float		FIGHTER_BREAK_FORMATION_DISTANCE = 200.0f;	// distance to target at which fighters break their formation
const float		FIGHTER_ROLL_RATE = (PI * 2) / TICKS_PER_SECOND;

//----------------------------------------------------------------------------

IMPLEMENT_FACTORY( NounFighter, NounSmartProjectile );
REGISTER_FACTORY_KEY( NounFighter, 4296314509346749013 );

BEGIN_PROPERTY_LIST( NounFighter, NounSmartProjectile )
	ADD_TRANSMIT_PROPERTY( m_nState );
	ADD_TRANSMIT_UPDATE_PROPERTY( m_Children );
END_PROPERTY_LIST();

NounFighter::NounFighter() : m_nState( ATTACK ), m_bDestroyed( false ), m_nUpdateTick( 0 )
{}

//----------------------------------------------------------------------------

void NounFighter::onDetaching()
{
	if ( m_nState == RETURN && !m_bDestroyed )
	{
		GadgetWeapon * pWeapon = weapon();
		if ( pWeapon != NULL )
			pWeapon->setAmmo( pWeapon->ammo() + 1 );
	}
	NounSmartProjectile::onDetaching();
}

//----------------------------------------------------------------------------

NounType NounFighter::nounType() const
{
	return TYPE_LOCAL_OBJECT;
}

void NounFighter::collision( Noun * pCollide, const Vector3 & intersect )
{
	if ( WidgetCast<NounBody>( pCollide ) )
	{
		if ( pCollide == parentBody() )
		{
			if ( m_nState != RETURN )
				return;

			// detach from the world
			setDetach();
			return;
		}

		float fDistance = (m_Position - pCollide->position()).magnitude();
		float R2 = radius() + pCollide->radius();

		if ( fDistance < R2 )
		{
			float fInside = R2 - fDistance;
			m_Position.y += fInside;
		}
		return;
	}
	else if ( WidgetCast<NounFighter>( pCollide ) )
	{
		// push this fighter outside the radius of the other fighter
		Vector3 delta( m_Position - pCollide->position() );
		float fDistance = delta.magnitude();
		float R2 = radius() + pCollide->radius();

		if ( fDistance < R2 )
		{
			delta.normalize();
			m_Position += delta * (R2 - fDistance);
		}
		return;
	}

	// call default collision handler
	NounSmartProjectile::collision( pCollide, intersect );
}

void NounFighter::simulate( dword nTick )
{
	if ( nTick >= m_nUpdateTick )
	{
		m_nUpdateTick = nTick + (TICKS_PER_SECOND * 1);
		switch( m_nState )
		{
		case ATTACK:
			if ( validateTarget( m_pTarget ) )
			{
				// calculate an intercept position
				Vector3 target;
				intercept( m_pTarget->worldPosition(), m_pTarget->worldVelocity(), target );

				// get the distance before we normalize
				Vector3 delta( target - worldPosition() );
				float distance = delta.magnitude();

				// normalize and turn towards our target
				delta.normalize();
				control( delta );

				// fire weapons!
				for(int i=0;i<childCount();i++)
				{
					NounGadget * pGadget = WidgetCast<NounGadget>( child(i) );
					if ( pGadget != NULL )
					{
						if ( WidgetCast<GadgetWeapon>( pGadget ) )
						{
							if ( pGadget->usable( m_pTarget, false ) )
								pGadget->use( tick(), m_pTarget, false );
						}
						else if ( WidgetCast<GadgetBeamWeapon>( pGadget ) )
						{
							if ( pGadget->usable( m_pTarget, false ) )
								pGadget->use( tick(), m_pTarget, false );
						}
						else
						{
							// Use ECCM, ECM, etc..
							if (! pGadget->useActive() )
								pGadget->use( tick(), m_pTarget, false );
						}
					}
				}

				if ( (distance - m_pTarget->radius()) < FIGHTER_EVADE_DISTANCE )
					setState( EVADE );
			}
			else
			{
				// target gone, roll and return home..
				setState( ROLL );
			}

			// check the distance from home, and determine when we need to turn around and head back to make it in time
			if ( parentBody() != NULL )
			{
				float fDistance = (parentBody()->worldPosition() - worldPosition()).magnitude();
				float fTime = fDistance / m_fVelocity;

				// once our life is less than the amount of time it will take to get back home,
				// we turn around and head back to our parent..
				if ( m_nLife < ((int)timeTick(fTime * 0.85f)) )
					setState( RETURN );
			}
			break;
		case EVADE:
			if ( validateTarget( m_pTarget ) )
			{
				Vector3 target( m_pTarget->worldPosition() );
				Vector3 delta( worldPosition() - target );
				float distance = delta.magnitude();

				delta.normalize();
				control( delta );

				if ( (distance - m_pTarget->radius()) > FIGHTER_ATTACK_DISTANCE )
					setState( ATTACK );
			}
			else
			{
				// target gone..
				setState( ROLL );
			}
			break;
		case ROLL:
			// celebration is over, return home
			setState( RETURN );
		case RETURN:
			if ( parentBody() )
			{
				// calculate the intercept position
				Vector3 target;
				intercept( parentBody()->worldPosition(), parentBody()->worldVelocity(), target );
				// get the delta from our current position
				Vector3 delta( target - worldPosition() );
				// control this fighter
				delta.normalize();
				control( delta );
			}
			else
			{
				// base lost... destroy this fighter..
				// TODO: let fighter look for a new home
				m_nLife = 0;
			}
			break;
		}
	}

	Vector3 vOrientation( orientation() );

	// update fighter roll
	if ( m_nState != ROLL )
		vOrientation.z -= (vOrientation.z * 0.05f);	
	else
		vOrientation.z += FIGHTER_ROLL_RATE * TICK_DURATION_S;
	
	float fPitch = Clamp( m_fPitch, -m_fTurnRate, m_fTurnRate ) * TICK_DURATION_S;
	m_fPitch -= fPitch;
	vOrientation.x += fPitch;

	float fYaw = Clamp( m_fYaw, -m_fTurnRate, m_fTurnRate ) * TICK_DURATION_S;
	m_fYaw -= fYaw;
	vOrientation.y += fYaw;

	setOrientation( vOrientation );
	setVelocity( m_Frame.k * m_fVelocity );

	// update weapons
	for(int i=0;i<childCount();i++)
	{
		NounGadget * pGadget = WidgetCast<NounGadget>( child(i) );
		if ( pGadget != NULL )
			pGadget->useEnergy( tick(), FIGHTER_ENERGY );
	}

	// Not calling NounSmartProjectile::simulate( dword nTick ) on purpose...
	NounProjectile::simulate( nTick );
}

void NounFighter::explode()
{
	m_bDestroyed = true;
	NounProjectile::explode();
}

//----------------------------------------------------------------------------

NounFighter::State NounFighter::state() const
{
	return (NounFighter::State)m_nState;
}

void NounFighter::setState( State state )
{
	m_nState = state;
}

//----------------------------------------------------------------------------
//EOF
